/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * assign3
 * Copyright (C)  Michael 2008 <mikey.morckos@gmail.com>
 * 
 * assign3 is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * assign3 is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "engine.h"

/* ORIENTATION OF EACH PIXEL */
double orients[1000][1000];

Engine :: Engine () 

: img_height(0),
  img_width(0)

{
	name1 = "Original";
    name2 = "Canny (Manual)";
	name3 = "Canny (CV)";
	
	img1 = 0;
	img2 = 0;
    img3 = 0;
	
	cvNamedWindow(name1, 4);
	cvNamedWindow(name2, 4);
    cvNamedWindow(name3, 4);
}

Engine :: ~Engine() {

	cout << "Closing..." << endl;
	
	cvReleaseImage (&img1);
	cvReleaseImage (&img2);
    cvReleaseImage (&img3);
	
	cvDestroyWindow(name1);
	cvDestroyWindow(name2);
    cvDestroyWindow(name3);
}	

/* START UP */
void Engine :: display_Image(const char* file_name) {

	this->file_name = file_name;

	if(file_name) {
		img1 = cvLoadImage (file_name, -1);
		
		if(!img1) 
		    cout <<  "Could not load image : " << file_name << endl;

		else {
			img_height = img1->height;
			img_width = img1->width;
			img_data.set_IMG(img1);
			
            img2 = cvCreateImage(cvGetSize(img1), IPL_DEPTH_8U, 1);
            img3 = cvCreateImage(cvGetSize(img1), IPL_DEPTH_8U, 1);
			
			cvShowImage(name1, img1);
		}	
	}
	else
		cout <<  "No file chosen!" << endl;
}

/*** CANNY ***/

/* ACQUIRING THE VIEWPORT (THE WINDOW IN THE IMAGE THE KERNEL IS COVERING) */
void Engine :: get_Viewport(int i, int j, int* i1_Gauss, int* i2_Gauss, int* j1_Gauss, int* j2_Gauss) {
	
	int i1 = 2, i2 = 2, j1 = 2, j2 = 2;
	int i_temp = i, j_temp = j;
	
	/* GETTING THE HEIGHT RANGE */
	
	/* GETTING THE MINIMUM */
	while(i_temp > 0 && i1 > 0) {
		i1--;
		i_temp--;
	}
	
	/* GETTING THE MAXIMUM */
	i_temp = i;
	while(i_temp + 1 < img_height && i2 < 4) {
		i2++;
		i_temp++;
	}
	
	/* GETTING THE WIDTH RANGE */
	
	/* GETTING THE MINIMUM */
	while(j_temp > 0 && j1 > 0) {
		j1--;
		j_temp--;
	}
	
	/* GETTING THE MAXIMUM */
	j_temp = j;
	while(j_temp + 1 < img_width && j2 < 4) {
		j2++;
		j_temp++;
	}
	
	*i1_Gauss = i1; *i2_Gauss = i2; 
	*j1_Gauss = j1; *j2_Gauss = j2;
}

/* APPLYING GAUSSIAN KERNEL */
void Engine :: apply_Gauss() {
	
	int i1_Gauss = 0, i2_Gauss = 0, j1_Gauss = 0, j2_Gauss = 0;
	int i_temp = 0, j_temp = 0;
	
	/*  5X5 GAUSSIAN KERNEL */
	float gauss_kernel [5][5] = {{1, 4, 7, 4, 1},              
	                              {4, 16, 26, 16, 4},
		                          {7, 26, 40, 26, 7},
		                          {4, 16, 26, 16, 4},
	                              {1, 4, 7, 4, 1}
	                             };

	CvScalar s;
	
	/* SMOOTHING THE IMAGE */
    for(int i = 0; i < img_height; i++) {
        for(int j = 0; j < img_width; j++) {  
			/* ACQUIRING THE HEIGHT AND WIDTH OF THE KERNEL TO BE APPLIED TO EACH PIXEL */
			get_Viewport(i, j, &i1_Gauss, &i2_Gauss, &j1_Gauss, &j2_Gauss);
			
			/* APPLYING THE KERNEL ON THE CURRENT PIXEL */
			for(int m = i1_Gauss; m <= i2_Gauss; m++) {
				i_temp = i - (2 - m);
				
				for(int n = j1_Gauss; n <= j2_Gauss; n++) {
					j_temp = j - (2 - n);
					
					/* RETRIEVING DATA FROM THE ORIGINAL IMAGE */
					s = cvGet2D(img1, i_temp, j_temp);
					
					/* MULTIPLYING EACH PIXEL WITH THE CORRESPONDING KERNEL VALUE */
					s.val[0] = (gauss_kernel[m][n] * (float)s.val[0]);
					s.val[1] = (gauss_kernel[m][n] * (float)s.val[1]);
					s.val[2] = (gauss_kernel[m][n] * (float)s.val[2]);
					
					/* RIGHTING RESULT PIXEL TO THE RESULT IMAGE */
					cvSet2D(img2, i_temp, j_temp, s);
				}	
			}
        }
    }
}

/* ROBERTS DETECTOR */
void Engine :: apply_Roberts_Detector () {
	
	CvScalar s1, s2, s3, s4, s_result;
	
	for(int i = 0; i < img_height; i++) {
		for(int j = 0; j < img_width; j++) {
			s_result = cvGet2D(img2, i, j);
			
			/* IF A BORDER PIXEL SET TO ZERO */
			if(i == img_height - 1 || j == img_width - 1) {
				s_result.val[0] = 0; s_result.val[1] = 0; s_result.val[2] = 0;
			}	
			
			else {
				/* Gx(i, j) */
				s1 = cvGet2D(img2, i, j);
		    	s2 = cvGet2D(img2, i+1, j+1);
			
				/* Gy(i, j) */
	    		s3 = cvGet2D(img2, i, j+1);
		    	s4 = cvGet2D(img2, i+1, j);
			
				/* MULTIPLYING THE RESULT BY 2 TO ENHANCE THE DETECTION */
		    	s_result.val[0] = 2 * (fabs(s1.val[0] - s2.val[0]) + fabs(s3.val[0] - s4.val[0]));
		    	s_result.val[1] = 2 * (fabs(s1.val[1] - s2.val[1]) + fabs(s3.val[1] - s4.val[1]));
	    		s_result.val[2] = 2 * (fabs(s1.val[2] - s2.val[2]) + fabs(s3.val[2] - s4.val[2]));
				
				/* RECORDING THE ORIENTATION OF EACH PIXEL */
				orients[i][j] = (double)(atan(fabs(s1.val[0] - s2.val[0]) / fabs(s3.val[0] - s4.val[0])) + (0.75 * CV_PI));
			}	
			
			cvSet2D(img2, i, j, s_result);
		}
	}
}

/* THINNING THE IMAGE BY SETTING ALL PIXELS BELOW THREHOLD TO ZERO */
void Engine :: apply_Threshold (int T2) {
	
	CvScalar s_result;
	
	for(int i = 0; i < img_height; i++) {
		for(int j = 0; j < img_width; j++) {
			s_result = cvGet2D(img2, i, j);
			
			/* IF A PIXEL IS BELOW T2 OR A BORDER PIXEL SET TO ZERO */
			if(s_result.val[0] < T2 || i == img_height - 1 || j == img_width - 1 || i == 0 || j == 0) {
				s_result.val[0] = 0;
				s_result.val[1] = 0;
				s_result.val[2] = 0;
				cvSet2D(img2, i, j, s_result);
			}
		}
	}	
}

/* DETERMINING LOCAL MAXIMA */
void Engine :: apply_Local_Maxima (int T1, int T2) {
	
	CvScalar s_result;	
	int tempi1 = 0, tempj1 = 0, tempi2 = 0, tempj2 = 0;
	
	for(int i = 0; i < img_height; i++) {
		for(int j = 0; j < img_width; j++) {
			s_result = cvGet2D(img2, i, j);
			
			/* SET ALL BORDER PIXELS TO ZERO */
			if(i == img_height - 1 || j == img_width - 1 || i == 0 || j == 0) {
				s_result.val[0] = 0; s_result.val[1] = 0; s_result.val[2] = 0;
			}
			/* BASED ON THE ORIENTTION CHECK IF THE PIXEL IS LOCAL MAXIMA */
			else if(s_result.val[0] >= T1 || (s_result.val[0] < T1 && s_result.val[0] >= T2)) {
				/* GET PIXEL PERPENDICULAR TO THE ORIENTATION */
				if(orients[i][j] < 0) {
					tempi1 = i;
					tempj1 = j+1;
					
					tempi2 = i+1;
					tempj2 = j;
				}
				else {
					tempi1 = i;
					tempj1 = j;
					
					tempi2 = i+1;
					tempj2 = j+1;
				}
				/* IF PIXEL IS A LOCAL MAXIMA SET TO 1 ELSE SET TO 0 */
				if((int)img_data[i][j].r > (int)img_data[tempi1][tempj1].r) {
					if((int)img_data[i][j].r > (int)img_data[tempi2][tempj2].r) {	
						s_result.val[0] = 255;
			            s_result.val[1] = 255;
			            s_result.val[2] = 255;
				    }
				    else {
						s_result.val[0] = 0;
				        s_result.val[1] = 0;
				        s_result.val[2] = 0;
					}	
			    }
			}
			
			cvSet2D(img2, i, j, s_result);	
		}
	}
}	

/* NON MAXIMUM SUPPRESSION */
void Engine :: apply_Suppression (int T1, int T2) {
	
	CvScalar s_result;
    Pix_Label img_labels[img_height][img_width];
	
	for(int i = 0; i < img_height; i++) {
		for(int j = 0; j < img_width; j++) {
			s_result = cvGet2D(img2, i, j);
			/* IF A PIXEL IS > T1 CALL THE SECOND SUPPRESSION METHOD */
			if(s_result.val[0] >= T1) {
				img_labels[i][j].set_Label(1);	
				apply_Suppression2(T1, T2, i, j);
			}
		}
	}
}

/* RECURSIVE SUPPRESSION */
void Engine :: apply_Suppression2 (int T1, int T2, int ix, int jy) {
	
	CvScalar s_result;
	s_result = cvGet2D(img2, ix, jy);
	
	s_result.val[0] = 255;
	s_result.val[1] = 255;
	s_result.val[2] = 255;
	
	cvSet2D(img2, ix, jy, s_result);
	
	for(int i = ix - 1; i < ix + 1; i++) {
		for(int j = jy - 1; j < jy + 1; j++) {
			s_result = cvGet2D(img2, ix, jy);
			
			if(!(i < 0) && !(j < 0) && !(i == ix) && !(j == jy)) {
				/* IF A SURROUNDING PIXEL IS BETWEEN T1 AND T2 IT'S CONSIDERED EDGES AND RECURSION IS CALLED AGAIN */ 
				if((s_result.val[0] < T1 && s_result.val[0] >= T2))
					/* RECURSION */
				    apply_Suppression2(T1, T2, i, j);
			}	
		}
	}	
}	

/* CANNY DETECTION ALGORITHM */
void Engine :: apply_Canny (int T1, int T2) {
	
	cout << "Canny..." << endl;
	
	/* GUASS */
	apply_Gauss();
	/* ROBERTS */
	apply_Roberts_Detector();
	/* THRESHOLDING THE IMAGE */
	apply_Threshold(T2);
	/* GETTING LOCAL MAXIMA */
	apply_Local_Maxima(T1, T2);
	/* NON-MAXIMAL SUPPRESSION */
	apply_Suppression(T1, T2);
	
	cvShowImage(name2, img2);

	/* BUILT-IN CANNY */
    apply_Canny_CV(T1, T2);
}

/* BUILT IN CANNY DETECTION */
void Engine :: apply_Canny_CV (int T1, int T2) {
	
	cvCanny(img1, img3, double(T1), double(T2), 3);
	cvShowImage(name3, img3);
} 

/**** CCLA ****/

/* HELPER FOR CCLA CHECKING FOR THE EIGHT CONNECTIVITY PIXELS */
void Engine :: check_equivelance (int i, int j, int* num, Pix_Pos* pix_pos) {
	
	int counter = 0;
	
	/* THE LEFT PIXEL */
	if(!((j - 1) < 0)) {
		if((int)img_data[i][j].r == (int)img_data[i][j - 1].r) {
			counter++;
			pix_pos->set_X(i);
			pix_pos->set_Y(j - 1);
			pix_pos++;
		}	
	}
	/* THE UPPER LEFT DIAGONAL PIXEL */
	if(!((i - 1) < 0)) {
		if(!((j - 1) < 0)) {
			if((int)img_data[i][j].r == (int)img_data[i - 1][j - 1].r) {
		    	counter++;
		    	pix_pos->set_X(i - 1);
	     		pix_pos->set_Y(j - 1);
	     		pix_pos++;
		    }
		}	
	}
	/* THE TOP PIXEL */
	if(!((i - 1) < 0)) {
		if((int)img_data[i][j].r == (int)img_data[i - 1][j].r) {
			counter++;
			pix_pos->set_X(i - 1);
			pix_pos->set_Y(j);
			pix_pos++;
		}
	}
	/* THE UPPER RIGHT DIAGONAL PIXEL */
	if(!((i - 1) < 0)) {
		if(!((j + 1) == img_width)) {
			if((int)img_data[i][j].r == (int)img_data[i - 1][j + 1].r) {
		    	counter++;
			    pix_pos->set_X(i - 1);
	    		pix_pos->set_Y(j + 1);
	    		pix_pos++;
		    }	
		}	
	}
	
	*num = counter;
}	

/* CONNECTED COMPONENTS LABELING ALGORITHM */
void Engine :: apply_CCLA() {

    cout << "CCLA..." << endl;
	
    /* LABELS */
	Pix_Label img_labels[img_height][img_width];
	/* EQUIVALENCES */
    Pix_Label equivalences[img_height * img_width];
	Pix_Label* equivals = &equivalences[0];
	int num_of_equivals = 0, label = 0;
	int num = 0, value = 0, val1 = 0, val2 = 0;
	Pix_Pos pix_pos[4];
	
	int target_label1 = 0, target_label2 = 0;
	
	bool flag = false, FL = false;
	Pix_Pos start, end;
	
	string rects, squas, circles;
	int num_of_rects = 0, num_of_squas = 0, num_of_circles = 0;
	
	/* FIRST SCAN */
	for(int i = 0; i < img_height; i++) {
		for(int j = 0; j < img_width; j++) {
			check_equivelance(i, j, &num, &pix_pos[0]);
			value = (int)img_data[i][j].r;
			
            (img_labels[i][j]).set_Value(value);

            /* IF NON OF THE SURROUNDING PIXELS MATCH MAKE A NEW LABEL */
			if(num == 0) {
				label++; 	
				(img_labels[i][j]).set_Label(label);
			}
            /* IF MORE THAN ONE PIXEL MATCHES SET THE LABEL OF EITHER ONE AND CHECK FOR EQUIVALENCES. IF ONE USE ITS LABEL */
			else {
				(img_labels[i][j]).set_Label((img_labels[(pix_pos[0]).get_X()][(pix_pos[0]).get_Y()]).get_Label());
					
				for(int i = 0; i < num - 1; i++) {
					val1 = ((img_labels[(pix_pos[i]).get_X()][(pix_pos[i]).get_Y()]).get_Label());
					val2 = ((img_labels[(pix_pos[i + 1]).get_X()][(pix_pos[i + 1]).get_Y()]).get_Label());
					
					/* RECORDING EQUIVALENCES */
					if(val1 != val2) {
						if(val1 < val2) {
							equivals->set_Label(val2);
					        equivals->set_Label2(val1);
						}
						else {
							equivals->set_Label(val1);
					        equivals->set_Label2(val2);
						}
					    
						equivals++;
						num_of_equivals++;
				    }
				}
			}		
	    }
	}
	
	/* SECOND SCAN. THE ALGORITHM WORKS BY REPLACING THE HIGHER VALUED LABELS WITH LOWER VALUED ONE
       WHILE STORING THE ORIGINAL LABEL VALUE OF EACH LABEL FOR FUTURE REPLACEMENT WITH THE CORRECT LABEL
       VALUE 
	*/
	for(int i = 0; i < num_of_equivals; i++) {
		target_label1 = equivalences[i].get_Label();
		target_label2 = equivalences[i].get_Label2();
		
		for(int i = 0; i < img_height; i++) {
		    for(int j = 0; j < img_width; j++) {
				if(img_labels[i][j].get_Origin() == target_label2) {
					target_label2 = img_labels[i][j].get_Label();
				    break;
			    }
			}
		}
		for(int i = 0; i < img_height; i++) {
		    for(int j = 0; j < img_width; j++) {
				if(img_labels[i][j].get_Label() == target_label1) {
					/* KEEPING TRACK OF THE ORIGINAL LABEL FOR EACH PIXEL */
					img_labels[i][j].set_Origin(img_labels[i][j].get_Label());
					img_labels[i][j].set_Label(target_label2);
				}	
			}
		}
	}
	
	/* EXTRACTION OF THE FINAL LABELS */
	int final_labels[label];
	int num_of_classes = 0;
	
	for(int x = 1; x <= label; x++) {
		FL = false;
		
		for(int i = 0; i < img_height; i++) {
			for(int j = 0; j < img_width; j++) {
				if(img_labels[i][j].get_Label() == x) {
					final_labels[num_of_classes] = x;
					num_of_classes++;
					FL = true;
					break;
				}	
			}
			
			if(FL)
			    break;
		}
	}
	
	for(int i = 0; i < 8; i++) {
		cout << final_labels[i] << endl;
	}	
	
	/* EXTRACTING FIGURES DATA. FOR EACH LABEL THE ALGORITHM SCANS FOR THE FIRST AND LAST PIXEL AS WELL AS THE
       PIXEL WITH THE LEAST WIDTH VALUE (FOR CIRCLE DETECTION). 
	*/
	for(int x = 1; x < num_of_classes; x++) {
		flag = false;
		
		for(int i = 0; i < img_height; i++) {
			for(int j = 0; j < img_width; j++) {
				if(img_labels[i][j].get_Label() == final_labels[x]) {
                    /* FIRST PIXEL IN A COMPONENT */
					if(!flag) {
						start.set_X(i);
					    start.set_Y(j);
						
						val1 = j;
						flag = true;
					}
                    /* LAST PIXEL IN A COMPONENT */
					else {
						end.set_X(i);
					    end.set_Y(j);
					}
                    /* LEAST WIDTH VALUE */
					if(val1 > j)
						val1 = j;
				}	
			}
		}
		
		/* IN CASE OF CIRCLES THE CORNER OF THE VIRTUAL SQUARE ENCLOSING THE CIRCLE WILL HAVE A DIFFERENT LABEL */
		if(img_labels[start.get_X()][val1].get_Label() != final_labels[x]) {
			circles.append(itos(((end.get_Y() - start.get_Y()) / 2) + start.get_Y())); circles.append(" ");
			circles.append(itos((end.get_X() - start.get_X()) / 2 + start.get_X())); circles.append(" ");
			circles.append(itos((end.get_X() - start.get_X()) / 2)); circles.append("\n");
			
			num_of_circles++;
		}
		/* IN CASE RECTANGLES/SQUARES THE DATA REQUIRED ARE CALCULATED FROM THE STARTING AND ENDING PIXELS */
		else {
			if((end.get_Y() - start.get_Y()) == (end.get_X() - start.get_X())) {
		        squas.append(itos(start.get_Y())); squas.append(" ");
			    squas.append(itos(start.get_X())); squas.append(" ");
			    squas.append(itos(end.get_X() - start.get_X())); squas.append("\n");
			
			    num_of_squas++;
		    }
		    else {
			    rects.append(itos(start.get_Y())); rects.append(" ");
			    rects.append(itos(start.get_X())); rects.append(" ");
			    rects.append(itos(end.get_Y() - start.get_Y())); rects.append(" ");
			    rects.append(itos(end.get_X() - start.get_X())); rects.append("\n");
			
			    num_of_rects++;
		    }
	    }		     
	}
	
	/* WRITING DATA TO FILE */
	output_file.open("Image_Data");
	
	output_file << num_of_squas;
	output_file << " Squares [X, Y, Side] \n";
	output_file << squas;
	output_file << "\n";
	
	output_file << num_of_rects;
	output_file << " Rectangles [X, Y, Side X, Side Y] \n";
	output_file << rects;
    output_file << "\n";
	
	output_file << num_of_circles;
	output_file << " Cirlces [X, Y, Radius] \n";
	output_file << circles;
	output_file.close();
	
	cout << "Wrote data to file : Image_Data\n" << endl;
}

